package fun.eliauk.treedemo.utils.time;





import com.alibaba.fastjson2.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class JobTimeUtil {
    /**
     * 上班时间
     */
    private LocalTime START_WORK_TIME;

    /**
     * 下班时间
     */
    private LocalTime END_WORK_TIME;

    /**
     * 节假日
     */
    private Set<LocalDate> HOLIDAYS = new HashSet<>();

    /**
     * 调休日
     */
    private Set<LocalDate> REST_DAYS =new HashSet<>();

    // 无参构造时 访问外部api 获得 节日 和 调休日期
    private static HttpURLConnection conn;

    public JobTimeUtil() {
        try {
            init();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void init() throws IOException {
        URL url = new URL("https://api.jiejiariapi.com/v1/holidays/2024");
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestProperty("User-Agent", "Mozilla/4.76");
        conn.setRequestMethod("GET");

        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String inputLine;
        StringBuilder content = new StringBuilder();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        String jsonStr = content.toString();
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        Set<String> strings = jsonObject.keySet();
        for (String string : strings) {
            Holiday holiday = jsonObject.getObject(string, Holiday.class);
            LocalDate date = LocalDate.parse(holiday.getDate());

            int year = date.getYear();
            int month = date.getMonthValue();
            int day = date.getDayOfMonth();
            LocalDate localDate = LocalDate.of(year, month, day);
            this.HOLIDAYS.add(localDate);
        }
        in.close();
        conn.disconnect();
    }

    /**
     * 上班时间配置
     *
     * @param beginHour   开始上班的 时 9
     * @param beginMinute 开始上班的 分 0
     * @param endHour     上班结束的 时 17
     * @param endMinute   上班结束 分 0
     */
    public void workConfig(Integer beginHour, Integer beginMinute, Integer endHour, Integer endMinute) {
        if (beginHour == null || beginMinute == null || endHour == null || endMinute == null) {
            throw new IllegalArgumentException("上班时间参数不能为空");
        }

        if (beginHour < 0 || beginHour > 23 || beginMinute < 0 || beginMinute > 59
                || endHour < 0 || endHour > 23 || endMinute < 0 || endMinute > 59) {
            throw new IllegalArgumentException("时间范围不合法");
        }

        LocalTime startWorkTime = LocalTime.of(beginHour, beginMinute);
        LocalTime endWorkTime = LocalTime.of(endHour, endMinute);

        if (startWorkTime.isAfter(endWorkTime)) {
            throw new IllegalArgumentException("上班开始时间应该早于结束时间");
        }

        this.START_WORK_TIME = startWorkTime;
        this.END_WORK_TIME = endWorkTime;
    }

    /**
     * 上班时间配置
     * 中间 ： 可用中文的也可以英文的
     *
     * @param beginTime 上班时分字符串 比如 09：00
     * @param endTime  下班时分字符串 比如 17：00
     */
    public void workConfig(String beginTime, String endTime) {
        if (beginTime == null || endTime == null) {
            throw new IllegalArgumentException("上班时间不能为空");
        }

        if (!beginTime.matches("\\d{2}[：:]\\d{2}") || !endTime.matches("\\d{2}[：:]\\d{2}")) {
            throw new IllegalArgumentException("时间格式不正确，正确格式为：HH:mm");
        }

        String[] beginStr = beginTime.split("[：:]");
        String[] endStr = endTime.split("[：:]");

        int beginHour = Integer.parseInt(beginStr[0]);
        int beginMinute = Integer.parseInt(beginStr[1]);
        int endHour = Integer.parseInt(endStr[0]);
        int endMinute = Integer.parseInt(endStr[1]);

        if (beginHour < 0 || beginHour > 23 || beginMinute < 0 || beginMinute > 59
                || endHour < 0 || endHour > 23 || endMinute < 0 || endMinute > 59) {
            throw new IllegalArgumentException("时间范围不合法");
        }

        LocalTime startWorkTime = LocalTime.of(beginHour, beginMinute);
        LocalTime endWorkTime = LocalTime.of(endHour, endMinute);

        if (startWorkTime.isAfter(endWorkTime)) {
            throw new IllegalArgumentException("上班开始时间应该早于结束时间");
        }

        this.START_WORK_TIME = startWorkTime;
        this.END_WORK_TIME = endWorkTime;
    }

    public void holidayConfig(HashSet<LocalDate> holiday) {
        this.HOLIDAYS.addAll(holiday);
    }

    public void setSTART_WORK_TIME(LocalTime START_WORK_TIME) {
        this.START_WORK_TIME = START_WORK_TIME;
    }

    public void setEND_WORK_TIME(LocalTime END_WORK_TIME) {
        this.END_WORK_TIME = END_WORK_TIME;
    }

    public void setHOLIDAYS(Set<LocalDate> HOLIDAYS) {
        this.HOLIDAYS = HOLIDAYS;
    }

    public LocalDateTime DateToLocalDateTime(Date date) {
        Instant instant = date.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        return instant.atZone(zoneId).toLocalDateTime();
    }

    public Date LocalDateTimeToDate(LocalDateTime localDate) {
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDate.atZone(zoneId);

        return Date.from(zdt.toInstant());
    }

    public LocalTime getSTART_WORK_TIME() {
        return START_WORK_TIME;
    }

    public LocalTime getEND_WORK_TIME() {
        return END_WORK_TIME;
    }

    public Set<LocalDate> getHOLIDAYS() {
        return HOLIDAYS;
    }

    /**
     * 获取申请截止时间
     *
     * @param createTime 申请时间
     * @param span       时限
     * @param timeUnit   时限单位
     * @return 截止时间
     */
    public LocalDateTime getDeadline(LocalDateTime createTime, Long span, TimeUnit timeUnit) {
        return deadline(createTime, spanToMinute(span, timeUnit));
    }

    /**
     * 获取申请截止时间
     *
     * @param createTime 申请时间
     * @param span       时限 (默认单位是分钟)
     * @return 截止时间
     */
    public LocalDateTime getDeadline(LocalDateTime createTime, Long span) {
        return deadline(createTime, spanToMinute(span, TimeUnit.MINUTES));
    }

    /**
     * @param createTime 申请时间
     * @param span       时限
     * @param unit       时限单位
     * @param workBegin  上班时间
     * @param workEnd    下班时间
     * @param holiday    节假日
     * @return 截止时间
     */
    public LocalDateTime getDeadline(LocalDateTime createTime,
                                     Long span, TimeUnit unit,
                                     LocalTime workBegin, LocalTime workEnd,
                                     Set<LocalDate> holiday) {
        setSTART_WORK_TIME(workBegin);
        setEND_WORK_TIME(workEnd);
        setHOLIDAYS(holiday);
        return getDeadline(createTime, span, unit);
    }

    /**
     * 统一将时限转换为分钟的单位
     *
     * @param span     时限长度
     * @param timeUnit 时间单位
     * @return 时间长度（minute）
     */
    public Long spanToMinute(Long span, TimeUnit timeUnit) {
        switch (timeUnit) {
            case DAYS:
                span = span * 12 * 60;
                break;
            case HOURS:
                span = span * 60;
                break;
            case MINUTES:
                span = span;
                break;
            case SECONDS:
                span = span / 60;
                break;
            case MILLISECONDS:
                span = span / 60 / 1000;
                break;
            case MICROSECONDS:
                span = span / 60 / 1000 / 1000;
                break;
            case NANOSECONDS:
                span = span / 60 / 1000 / 1000 / 1000;
                break;
        }
        return span;
    }


    // 判断时间是否在上班时间内
    private Boolean inWork(LocalDateTime dateTime) {
        LocalTime time = dateTime.toLocalTime(); // 时间
        LocalDate date = dateTime.toLocalDate(); // 日期
        DayOfWeek dayOfWeek = dateTime.getDayOfWeek(); //星期

        return !time.isBefore(this.START_WORK_TIME)     //不是上班前
                && !time.isAfter(this.END_WORK_TIME)    //不是下班后
                && dayOfWeek != DayOfWeek.SATURDAY //不是周六
                && dayOfWeek != DayOfWeek.SUNDAY   //不是周日
                && !HOLIDAYS.contains(date);       //不是节假日
    }

    // 获取下次上班的开始时间
    private LocalDateTime nextWorkTime(LocalDateTime createTime) {
        // 下次上班时间分两种情况
        // 一种是创建时间在今天上班之前的 --> 今天的上班时间（或者今天不上班往后延）
        // 一种是创建时间在今天上班之后的 --> 明天的上班时间（或者明天不上班往后延）

        // 今天上班的开始时间
        LocalDateTime tempTime = createTime
                .withHour(START_WORK_TIME.getHour())
                .withMinute(START_WORK_TIME.getMinute())
                .withSecond(0)
                .withNano(0);

        // 今天上班
        if (inWork(tempTime)) {
            // 今天上班 且 创建时间在上班开始之前的 则返回今天的上班开始时间
            // 今天上班 且 创建时间在上班开始之后的 则相当于今天不上班
            return createTime.isBefore(tempTime) ? tempTime : findNextWorkTime(tempTime);
        } else {// 今天不上班
            return findNextWorkTime(tempTime);
        }
    }

    private LocalDateTime findNextWorkTime(LocalDateTime dateTime) {
        LocalDateTime nextWorkTime = dateTime;
        do {// 二话不说先加一天，如果还不是工作时间 则再加一天……
            nextWorkTime = nextWorkTime.plusDays(1)
                    .withHour(START_WORK_TIME.getHour())
                    .withMinute(START_WORK_TIME.getMinute());
        } while (!inWork(nextWorkTime));

        return nextWorkTime;
    }

    /**
     * @param creatTime 创建时间
     * @param span      时限单位为分钟
     * @return 计算截止时间
     */
    private LocalDateTime deadline(LocalDateTime creatTime, Long span) {
        Boolean in = inWork(creatTime); //创建时间是否在上班时间

        // 在上班时间内，需要考虑今天上班时间能不能走完 span
        // 能走完就直接返回 若不能则需特殊处理
        if (in) {
            LocalDateTime plusTime = creatTime.plusMinutes(span);
            if (inWork(plusTime) && plusTime.toLocalDate().equals(creatTime.toLocalDate())) { //需要保证 是今天能完成
                return plusTime;
            } else {
                return handlePass(creatTime, span);
            }
        }
        // 不在上班时间内则需要把全部的span给下次上班走
        // 可以考虑这种情况的创建时间是下次上班的开始时间
        else {
            return handlePass(creatTime, span);
        }
    }

    /**
     * 处理时限跨越了上班时间的情况
     *
     * @param creatTime
     * @param span
     * @return
     */
    private LocalDateTime handlePass(LocalDateTime creatTime, Long span) {
        while (span > 0) {
            // 创建时间在上班时间内
            if (inWork(creatTime)) {
                // 今天的下班时间
                LocalDateTime endOfWorkDay = creatTime.withHour(END_WORK_TIME.getHour())
                        .withMinute(END_WORK_TIME.getMinute())
                        .withSecond(0)
                        .withNano(0);

                // 离下班时间还有多久即今天还要上多久的班
                long between = ChronoUnit.MINUTES.between(creatTime, endOfWorkDay);

                // 如果时限小于剩余的上班时长那么 创建时间 + 时限 就是截止时间
                if (span <= between) {
                    return creatTime.plusMinutes(span);
                }
                // 如果时限大于剩余的上班时长，那么 新时限 = 原时限 - 剩余的上班时间
                // 可以考虑是 截止时间 = 下次上班时间 + 新时限
                creatTime = nextWorkTime(creatTime); // 把创建认为是下次上班时间
                span -= between;
            } else {
                // 创建时间不在上班时间内，
                // 可以考虑 截止时间 = 下次上班时间 + 全部时限
                creatTime = nextWorkTime(creatTime);
            }
        }
        return creatTime; // 正常下其实不会有这中情况
        //throw new RuntimeException("请检查传入参数");
    }


    public static void main(String[] args) throws IOException {
        // 假设申请时间为当前时间
        LocalDateTime createTime = LocalDateTime.now();
        System.out.println("申请开始时间========>" + createTime);


        // 计算到期时间
        JobTimeUtil util = new JobTimeUtil();
        util.workConfig(8, 0, 16, 0);
        HashSet<LocalDate> holiday = new HashSet<>();
        holiday.add(LocalDate.of(2024, 5, 30));
        holiday.add(LocalDate.of(2024, 5, 31));
        holiday.add(LocalDate.of(2024, 6, 1));
        holiday.add(LocalDate.of(2024, 6, 2));
        holiday.add(LocalDate.of(2024, 6, 3));
        holiday.add(LocalDate.of(2024, 6, 4));
        util.holidayConfig(holiday);
        LocalDateTime deadline = util.getDeadline(LocalDateTime.now(), 24L, TimeUnit.HOURS);


        // 到期时间
        System.out.println("申请截止时间========>" + deadline);
    }

}